

import { BaseWsClient, BaseWsClientOptions } from "tsrpc-base-client";
import { ServiceProto } from "tsrpc-proto";

import { serviceProto as gameServiceProto, ServiceType as gameServiceType } from "./protocols/serviceProto";
import { AWsClient } from "../tsgf/AClient";
import { logger } from "../tsgf/logger";
import { IResult, Result } from "../tsgf/Result";
import { IPlayerAuthPara, IPlayerInfo } from "../tsgf/player/IPlayerInfo";
import { ReqAuthorize } from "./protocols/PtlAuthorize";
import { IRoomInfo } from "../tsgf/room/IRoomInfo";
import { IRecvRoomMsg, IRoomMsg } from "../tsgf/room/IRoomMsg";
import { IAfterFrames, IGameSyncFrame, IPlayerInputOperate } from "../tsgf/room/IGameFrame";

declare module globalThis {
    /**调试用*/
    let __currGameClient: GameClient;
}

export class GameClient extends AWsClient<gameServiceType>{

    public playerToken: string;
    public playerId: string;

    protected _currRoomInfo: IRoomInfo | null = null;
    /**当前所在的房间, 各种操作会自动维护本属性值为最新*/
    public get currRoomInfo(): IRoomInfo | null {
        return this._currRoomInfo;
    }

    /**
     * 断线重连等待秒数
     */
    public reconnectWaitSec = 2;
    protected reconnectTimerHD: any;

    /**当断线时触发,返回是否重连,是的话,reconnectWaitSec秒后重连, 未配置则不自动重连*/
    public disconnectHandler?: () => boolean;
    /**断线重连最终有结果时触发(终于连上了,或者返回不继续尝试了)*/
    public onReconnectResult?: (succ: boolean, err: string | null) => void;
    /**当接收到房间消息时触发*/
    public onRecvRoomMsg?: (roomMsg: IRecvRoomMsg) => void;
    /**【在房间中才能收到】玩家加入当前房间（自己操作的不触发）*/
    public onPlayerJoinRoom?: (player: IPlayerInfo, roomInfo: IRoomInfo) => void;
    /**【在房间中才能收到】玩家退出当前房间（自己操作的不触发）*/
    public onPlayerLeaveRoom?: (player: IPlayerInfo, roomInfo: IRoomInfo) => void;
    /**【在房间中才能收到】当前房间被解散（自己操作的不触发）*/
    public onDismissRoom?: (roomInfo: IRoomInfo) => void;
    /**【在房间中才能收到】房间中开始帧同步了*/
    public onStartFrameSync?: (roomInfo: IRoomInfo, startPlayer: IPlayerInfo) => void;
    /**【在房间中才能收到】房间中停止帧同步了*/
    public onStopFrameSync?: (roomInfo: IRoomInfo, stopPlayer: IPlayerInfo) => void;
    /**【在房间中才能收到】房间中收到一个同步帧*/
    public onRecvFrame?: (syncFrame: IGameSyncFrame) => void;
    /**【在房间中才能收到】服务端要求玩家上传状态同步数据 (调用 playerSendSyncState 方法)*/
    public onRequirePlayerSyncState?: () => void;
    /**【在房间中才能收到】玩家加入当前房间（自己操作的不触发）*/
    public onChangePlayerNetworkState?: (player: IPlayerInfo) => void;

    /**
     * @date 2022/5/10 - 17:18:57
     *
     * @constructor
     * @param {string} playerToken 服务端调用大厅授权接口，获得玩家授权令牌
     * @param {(proto: ServiceProto<gameServiceType>, options?: Partial<BaseWsClientOptions>) => BaseWsClient<gameServiceType>} buildClient
     * @param {string} serverUrl
     */
    constructor(playerToken: string, serverUrl: string,
        buildClient: (proto: ServiceProto<gameServiceType>, options?: Partial<BaseWsClientOptions>) => BaseWsClient<gameServiceType>) {
        super(buildClient, gameServiceProto, {
            server: serverUrl,
            json: false,
            logger: logger,
        });
        this.playerToken = playerToken;
        this.playerId = "";
        //设置断线重连的中间件
        this.client.flows.postDisconnectFlow.push(async v => {
            //如果都没连上过就断开,那么忽略
            if (!this.playerId) return v;
            //判断是否需要重连
            if (!v.isManual && this.disconnectHandler?.call(this)) {
                this.client.logger?.error('连接已断开,等待' + this.reconnectWaitSec + '秒后自动重连');
                if (this.reconnectTimerHD) clearTimeout(this.reconnectTimerHD);
                this.reconnectTimerHD = setTimeout(async () => this.startReconnect(true), this.reconnectWaitSec * 1000);
            } else {
                this.client.logger?.error('连接已断开');
            }
            return v;
        });
        this.client.listenMsg("NotifyRoomMsg", (msg) => {
            this.onRecvRoomMsg?.call(this, msg.recvRoomMsg);
        });
        this.client.listenMsg("NotifyJoinRoom", (msg) => {
            this._currRoomInfo = msg.roomInfo;
            let joinPlayer = this._currRoomInfo.playerList.find(p => p.playerId === msg.joinPlayerId)!;
            this.onPlayerJoinRoom?.call(this, joinPlayer, msg.roomInfo);
        });
        this.client.listenMsg("NotifyLeaveRoom", (msg) => {
            this._currRoomInfo = msg.roomInfo;
            this.onPlayerLeaveRoom?.call(this, msg.leavePlayerInfo, msg.roomInfo);
        });
        this.client.listenMsg("NotifyDismissRoom", (msg) => {
            this._currRoomInfo = null;
            this.onDismissRoom?.call(this, msg.roomInfo);
        });
        this.client.listenMsg("NotifyStartFrameSync", (msg) => {
            this._currRoomInfo = msg.roomInfo;
            this.onStartFrameSync?.call(this, msg.roomInfo, msg.roomInfo.playerList.find(p => p.playerId === msg.startPlayerId)!);
        });
        this.client.listenMsg("NotifyStopFrameSync", (msg) => {
            this._currRoomInfo = msg.roomInfo;
            this.onStopFrameSync?.call(this, msg.roomInfo, msg.roomInfo.playerList.find(p => p.playerId === msg.stopPlayerId)!);
        });
        this.client.listenMsg("NotifySyncFrame", (msg) => {
            this.onRecvFrame?.call(this, msg.syncFrame);
        });
        this.client.listenMsg("RequirePlayerSyncState", (msg) => {
            this.onRequirePlayerSyncState?.call(this);
        });
        this.client.listenMsg("NotifyChangePlayerNetworkState", (msg) => {
            this._currRoomInfo = msg.roomInfo;
            let player = this._currRoomInfo.playerList.find(p => p.playerId === msg.changePlayerId)!;
            this.onChangePlayerNetworkState?.call(this, player);
        });


        globalThis.__currGameClient = this;
    }
    public async disconnect(): Promise<void> {
        if (this.playerId || this.client.isConnected) {
            this.playerId = '';
            this.playerToken = '';
            this._currRoomInfo = null;
            await this.client.sendMsg("Disconnect", {});
            await this.client.disconnect();
        }
        this.stopReconnect();
    }


    protected stopReconnect(): void {
        if (this.reconnectTimerHD) {
            clearTimeout(this.reconnectTimerHD);
            this.reconnectTimerHD = null;
        }
    }
    protected async startReconnect(failReTry = true): Promise<boolean> {
        const result = await this.reconnect();
        // 重连也错误，弹出错误提示
        if (result.succ) {
            this.client.logger?.log('重连成功!');
            this.onReconnectResult?.call(this, true, null);
            return true;
        }
        //如果是逻辑拒绝则不需要重连
        if (!this.playerToken || result.code == 5001) failReTry = false;

        if (failReTry && this.disconnectHandler?.call(this)) {
            this.client.logger?.error('重连失败:' + result.err + ' ' + this.reconnectWaitSec + '秒后自动重连!');
            if (this.reconnectTimerHD) clearTimeout(this.reconnectTimerHD);
            this.reconnectTimerHD = setTimeout(() => this.startReconnect(failReTry), this.reconnectWaitSec * 1000);
        } else {
            this.client.logger?.error('重连失败:' + result.err);
            this.disconnect();
            this.onReconnectResult?.call(this, false, result.err);
        }
        return false;
    }
    /**
     * 断线重连, 失败的话要看code,5001表示逻辑拒绝,不需要重连
     * @returns  
     */
    public async reconnect(): Promise<IResult<null>> {
        const connectRet = await this.client.connect();
        if (!connectRet.isSucc) {
            return Result.buildErr("连接失败:" + connectRet.errMsg);
        }
        const loginRet = await this.client.callApi("Reconnect", {
            playerToken: this.playerToken,
        });
        if (!loginRet.isSucc) {
            return Result.buildErr(loginRet.err.message, (loginRet.err?.code ?? 1) as number);
        }
        this._currRoomInfo = loginRet.res.currRoomInfo;
        return Result.buildSucc(null);
    }


    /**
     * 登录到游戏服务器, 失败则断开连接并清理数据
     * @param authPara 
     * @returns  
     */
    public async authorize(authPara?: IPlayerAuthPara): Promise<IResult<null>> {
        const connectRet = await this.client.connect();
        if (!connectRet.isSucc) {
            return Result.buildErr("连接失败:" + connectRet.errMsg);
        }
        let req: ReqAuthorize = (authPara as ReqAuthorize) ?? ({} as ReqAuthorize);
        req.playerToken = this.playerToken;
        const loginRet = await this.client.callApi("Authorize", req);
        if (!loginRet.isSucc) {
            this.disconnect();
            return Result.buildErr(loginRet.err.message, (loginRet.err?.code ?? 1) as number);
        }
        this.playerId = loginRet.res.playerInfo.playerId;
        return Result.buildSucc(null);
    }


    /**
     * 进房间
     * 错误码:
     * 1001:房间人数已满
     * 1004:房间不存在
     * @param roomId 
     * @returns  
     */
    public async joinRoom(roomId: string): Promise<IResult<IRoomInfo>> {
        const ret = await this.client.callApi("JoinRoom", {
            roomId: roomId
        });
        if (!ret.isSucc) {
            return Result.buildErr(ret.err.message, (ret.err?.code ?? 1) as number);
        }
        this._currRoomInfo = ret.res.roomInfo;
        return Result.buildSucc(ret.res.roomInfo);
    }
    /**
     * 退出当前房间
     * @returns  
     */
    public async leaveRoom(): Promise<IResult<null>> {
        const ret = await this.client.callApi("LeaveRoom", {
        });
        if (!ret.isSucc) {
            return Result.buildErr(ret.err.message, (ret.err?.code ?? 1) as number);
        }
        this._currRoomInfo = null;
        return Result.buildSucc(null);
    }
    /**
     * 【仅房主】解散当前房间
     * @param roomId 
     * @returns  
     */
    public async dismissRoom(): Promise<IResult<IRoomInfo>> {
        if (!this._currRoomInfo) return Result.buildErr('当前不在房间中！');
        const ret = await this.client.callApi("DismissRoom", {
            roomId: this._currRoomInfo.roomId
        });
        if (!ret.isSucc) {
            return Result.buildErr(ret.err.message, (ret.err?.code ?? 1) as number);
        }
        this._currRoomInfo = null;
        return Result.buildSucc(ret.res.roomInfo);
    }


    /**
     * 发送房间消息（自定义消息），可以指定房间里的全部玩家或部分玩家或其他玩家
     * @date 2022/5/11 - 16:04:30
     *
     * @public
     * @async
     * @param {IRoomMsg} roomMsg 
     * @returns {Promise<IResult<null>>}
     */
    public async sendRoomMsg(roomMsg: IRoomMsg): Promise<IResult<null>> {
        const ret = await this.client.callApi("SendRoomMsg", {
            roomMsg: roomMsg
        });
        if (!ret.isSucc) {
            return Result.buildErr(ret.err.message, (ret.err?.code ?? 1) as number);
        }
        return Result.buildSucc(null);
    }

    /**
     * 发送房间消息（自定义消息），可以指定房间里的全部玩家或部分玩家或其他玩家
     * @date 2022/5/11 - 16:04:30
     *
     * @public
     * @async
     * @param {IRoomMsg} roomMsg 
     * @returns {Promise<IResult<null>>}
     */
    public async startFrameSync(): Promise<IResult<null>> {
        const ret = await this.client.callApi("StartFrameSync", {
        });
        if (!ret.isSucc) {
            return Result.buildErr(ret.err.message, (ret.err?.code ?? 1) as number);
        }
        return Result.buildSucc(null);
    }
    /**
     * 发送房间消息（自定义消息），可以指定房间里的全部玩家或部分玩家或其他玩家
     * @date 2022/5/11 - 16:04:30
     *
     * @public
     * @async
     * @param {IRoomMsg} roomMsg 
     * @returns {Promise<IResult<null>>}
     */
    public async stopFrameSync(): Promise<IResult<null>> {
        const ret = await this.client.callApi("StopFrameSync", {
        });
        if (!ret.isSucc) {
            return Result.buildErr(ret.err.message, (ret.err?.code ?? 1) as number);
        }
        return Result.buildSucc(null);
    }

    /**
     * 发送玩家输入帧(加入到下一帧的操作列表)
     * @date 2022/5/11 - 16:04:30
     *
     * @public
     * @async
     * @param {IPlayerInputOperate[]} inpOperates 
     * @returns {Promise<IResult<null>>}
     */
    public async playerInpFrame(inpOperates: IPlayerInputOperate[]): Promise<IResult<null>> {
        const ret = await this.client.sendMsg("PlayerInpFrame", {
            operates: inpOperates
        });
        if (!ret.isSucc) {
            return Result.buildErr(ret.err.message, (ret.err?.code ?? 1) as number);
        }
        return Result.buildSucc(null);
    }
    /**
     * 请求追帧数据(当前的所有帧数据[+同步状态数据])
     * @date 2022/5/17 - 14:45:57
     *
     * @public
     * @async
     * @returns {Promise<IResult<null>>}
     */
    public async requestAfterFrames(): Promise<IResult<IAfterFrames>> {
        const ret = await this.client.callApi("RequestAfterFrames", {
        });
        if (!ret.isSucc) {
            return Result.buildErr(ret.err.message, (ret.err?.code ?? 1) as number);
        }
        return Result.buildSucc(ret.res);
    }
    /**
     * 自主请求帧数组
     * @date 2022/5/17 - 14:45:57
     *
     * @public
     * @async
     * @param {number} beginFrameIndex 起始帧索引(包含)
     * @param {number} endFrameIndex 结束帧索引(包含)
     * @returns {Promise<IResult<null>>}
     */
    public async requestFrames(beginFrameIndex: number, endFrameIndex: number): Promise<IResult<IGameSyncFrame[]>> {
        const ret = await this.client.callApi("RequestFrames", {
            beginFrameIndex: beginFrameIndex,
            endFrameIndex: endFrameIndex,
        });
        if (!ret.isSucc) {
            return Result.buildErr(ret.err.message, (ret.err?.code ?? 1) as number);
        }
        return Result.buildSucc(ret.res.frames);
    }



    /**
     * 玩家发送本地的同步状态数据(有启用状态同步的时候才可以用)
     * @date 2022/5/17 - 14:45:57
     *
     * @public
     * @async
     * @param {object} stateData
     * @param {number} stateFrameIndex
     * @returns {Promise<IResult<null>>}
     */
    public async playerSendSyncState(stateData: object, stateFrameIndex: number): Promise<IResult<null>> {
        const ret = await this.client.sendMsg("PlayerSendSyncState", {
            stateData: stateData,
            stateFrameIndex: stateFrameIndex,
        });
        if (!ret.isSucc) {
            return Result.buildErr(ret.err.message, (ret.err?.code ?? 1) as number);
        }
        return Result.buildSucc(null);
    }


}

declare global {

    /**
     * [需要实际客户端实现的函数]获取游戏连接客户端
     * @date 2022/2/18 - 下午11:31:49
     *
     * @param {string} serverUrl
     * @returns {GameClient}
     */
    function getGameClient(playerToken: string, serverUrl: string): GameClient;
}